//
// Copyright (c) 2010 xiaoningyb (xiaoningyb@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef TINYSHARING_CONNECTION_H
#define TINYSHARING_CONNECTION_H

#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/bind.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <string>
#include <sstream>

#include "message_format.h"
#include "request_handler.h"

namespace tinysharing
{   
    /// Represents a single connection from a client.
    class connection
        : public boost::enable_shared_from_this<connection>,
          private boost::noncopyable
    {
    public:
        /// Construct a connection with the given io_service.
        explicit connection(boost::asio::io_service& io_service,
                            request_handler& handler)
            : socket_(io_service), request_handler_(handler)
        {
        }

        ~connection()
        {
        }

        /// Get the socket associated with the connection.
        boost::asio::ip::tcp::socket& socket()
        {
            return socket_;
        }
        
        /// Start the first asynchronous operation for the connection.
        void start()
        {
        }
        
    public:
        template <typename T, typename Handler>
        void async_write(sharing_message_header& header, const T& t, Handler handler)
        {
            // Serialize the data first so we know how large it is.
            std::ostringstream data_stream;
            boost::archive::text_oarchive data_archive(data_stream);
            data_archive << t;
            outbound_data_ = data_stream.str();

            // Format the header.
            header.message_size = outbound_data_.size();        
            std::ostringstream header_stream;
            boost::archive::text_oarchive header_archive(header_stream);
            header_archive << header;        
            if (!header_stream || header_stream.str().size() != sharing_message_header_length)
            {
                // Something went wrong, inform the caller.
                boost::system::error_code error(boost::asio::error::invalid_argument);
                socket_.io_service().post(boost::bind(handler, error));
                return;
            }
            outbound_header_ = header_stream.str();

            // Write the serialized data to the socket. We use "gather-write" to send
            // both the header and the data in a single write operation.
            std::vector<boost::asio::const_buffer> buffers;
            buffers.push_back(boost::asio::buffer(outbound_header_));
            buffers.push_back(boost::asio::buffer(outbound_data_));
            boost::asio::async_write(socket_, buffers, handler);
        }


        template <typename T, typename Handler>
        void async_read(T& t, Handler handler)
        {
            // Issue a read operation to read exactly the number of bytes in a header.
            void (connection::*f)(const boost::system::error_code&, T&, boost::tuple<Handler>)
                = &connection::handle_read_header<T, Handler>;
            boost::asio::async_read(socket_, boost::asio::buffer(inbound_header_),
                                    boost::bind(f, this, boost::asio::placeholders::error,
                                                boost::ref(t), boost::make_tuple(handler)));
        }

        template <typename T, typename Handler>
        void handle_read_header(const boost::system::error_code& e, 
                                T& t, boost::tuple<Handler> handler)
        {
            if (e)
            {
                boost::get<0>(handler)(e);
            }
            else
            {
                // Extract the data structure from the data just received.
                sharing_message_header header;                
                try
                {
                    std::string archive_data(inbound_header_, sharing_message_header_length);
                    std::istringstream archive_stream(archive_data);
                    boost::archive::text_iarchive archive(archive_stream);
                    archive >> header;
                }
                catch (std::exception& e)
                {
                    // Unable to decode data.
                    boost::system::error_code error(boost::asio::error::invalid_argument);
                    boost::get<0>(handler)(error);
                    return;
                }

                // Start an asynchronous call to receive the data.
                inbound_data_.resize(header.message_size);
                void (connection::*f)(const boost::system::error_code&, 
                                      T&, boost::tuple<Handler>)
                    = &connection::handle_read_data<T, Handler>;
                boost::asio::async_read(socket_, boost::asio::buffer(inbound_data_),
                                        boost::bind(f, this, boost::asio::placeholders::error,
                                                    boost::ref(t), handler));
            }
        }

        template <typename T, typename Handler>
        void handle_read_data(const boost::system::error_code& e, 
                              T& t, boost::tuple<Handler> handler)
        {
            if (e)
            {
                boost::get<0>(handler)(e);
            }
            else
            {
                // Extract the data structure from the data just received.
                try
                {
                    std::string archive_data(&inbound_data_[0], inbound_data_.size());
                    std::istringstream archive_stream(archive_data);
                    boost::archive::text_iarchive archive(archive_stream);
                    archive >> t;
                }
                catch (std::exception& e)
                {
                    // Unable to decode data.
                    boost::system::error_code error(boost::asio::error::invalid_argument);
                    boost::get<0>(handler)(error);
                    return;
                }

                // Inform caller that data has been received ok.
                boost::get<0>(handler)(e);
        }
    }

    private:
        /// Holds an outbound header.
        std::string outbound_header_;

        /// Holds the outbound data.
        std::string outbound_data_;

        /// Holds an inbound header.
        char inbound_header_[sharing_message_header_length];

        /// Holds the inbound data.
        std::vector<char> inbound_data_;
        
        /// Socket for the connection.
        boost::asio::ip::tcp::socket socket_;

        /// The handler used to process the incoming request.
        request_handler& request_handler_;
    };

    typedef boost::shared_ptr<connection> connection_ptr;
    
} // namespace tinysharing

#endif // TINYSHARING_CONNECTION_H
